/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <X11/Xlib.h>
#include <glib.h>
#include "../toolkit/prop.h"
#include "../toolkit/display.h"
#include "misc.h"
#include "wm.h"
#include "window.h"
#include "geom.h"
#include "stacking.h"
#include "client.h"
#include "screen.h"
#include "event.h"
#include "focus.h"
#include "frame.h"
#include "debug.h"
GList *stacking_list = NULL;
static GList *stacking_list_tail = NULL;
static gboolean pause_changes = FALSE;

static void do_restack(GList *wins, GList *before)
{
	GList *it;
	Window *win;
	gint i;
#ifdef DEBUG
	GList *next;
	g_assert(wins);
	for(it = wins; it; it = next) {
		next = g_list_next(it);
		if(!next)
			break;
		g_assert(window_layer(it->data) == window_layer(next->data));
	}
	if(before)
		g_assert(window_layer(it->data) >= window_layer(before->data));
#endif
	win = g_new(Window, g_list_length(wins) + 1);
	if(before == stacking_list)
		win[0] = screen_support_win;
	else if(!before)
		win[0] = window_top(g_list_last(stacking_list)->data);
	else
		win[0] = window_top(g_list_previous(before)->data);
	for(i = 1, it = wins; it; ++i, it = g_list_next(it)) {
		win[i] = window_top(it->data);
		g_assert(win[i] != None);
		stacking_list = g_list_insert_before(stacking_list, before, it->data);
	}
#ifdef DEBUG
	for(it = stacking_list;; it = next) {
		next = g_list_next(it);
		if(!next)
			break;
		g_assert(window_layer(it->data) >= window_layer(next->data));
	}
#endif
	if(!pause_changes)
		XRestackWindows(t_display, win, i);
	g_free(win);
}

void stacking_temp_raise(struct wm_window *window)
{
	Window win[2];
	GList *it;
	gulong start;
	g_assert(window_layer(window) < WM_STACKING_LAYER_INTERNAL);
	win[0] = screen_support_win;
	for(it = stacking_list; it; it = g_list_next(it)) {
		struct wm_window *w = it->data;
		if(window_layer(w) >= WM_STACKING_LAYER_INTERNAL)
			win[0] = window_top(w);
		else
			break;
	}
	win[1] = window_top(window);
	start = event_start_ignore_all_enters();
	XRestackWindows(t_display, win, 2);
	event_end_ignore_all_enters(start);
	pause_changes = TRUE;
}

void stacking_restore(void)
{
	Window *win;
	GList *it;
	gint i;
	gulong start;
	win = g_new(Window, g_list_length(stacking_list) + 1);
	win[0] = screen_support_win;
	for(i = 1, it = stacking_list; it; ++i, it = g_list_next(it))
		win[i] = window_top(it->data);
	start = event_start_ignore_all_enters();
	XRestackWindows(t_display, win, i);
	event_end_ignore_all_enters(start);
	g_free(win);
	pause_changes = FALSE;
}

static void do_raise(GList *wins)
{
	GList *it;
	GList *layer[WM_NUM_STACKING_LAYERS] = { NULL };
	gint i;
	for(it = wins; it; it = g_list_next(it)) {
		enum wm_stacking_layer l;
		l = window_layer(it->data);
		layer[l] = g_list_append(layer[l], it->data);
	} it = stacking_list;
	for(i = WM_NUM_STACKING_LAYERS - 1; i >= 0; --i) {
		if(layer[i]) {
			for(; it; it = g_list_next(it)) {
				if(window_layer(it->data) <= (enum wm_stacking_layer)i)
					break;
			}
			do_restack(layer[i], it);
			g_list_free(layer[i]);
		}
	}
}

static void do_lower(GList *wins)
{
	GList *it;
	GList *layer[WM_NUM_STACKING_LAYERS] = { NULL };
	gint i;
	for(it = wins; it; it = g_list_next(it)) {
		enum wm_stacking_layer l;
		l = window_layer(it->data);
		layer[l] = g_list_append(layer[l], it->data);
	} it = stacking_list;
	for(i = WM_NUM_STACKING_LAYERS - 1; i >= 0; --i) {
		if(layer[i]) {
			for(; it; it = g_list_next(it)) {
				if(window_layer(it->data) < (enum wm_stacking_layer)i)
					break;
			}
			do_restack(layer[i], it);
			g_list_free(layer[i]);
		}
	}
}

static void restack_windows(struct wm_client *selected, gboolean raise)
{
	GList *it, *last, *below, *above, *next;
	GList *wins = NULL;
	GList *group_helpers = NULL;
	GList *group_modals = NULL;
	GList *group_trans = NULL;
	GList *modals = NULL;
	GList *trans = NULL;
	if(raise) {
		struct wm_client *p;
		while(selected->modal && (p = client_direct_parent(selected)))
			selected = p;
	}
	it = g_list_find(stacking_list, selected);
	g_assert(it);
	stacking_list = g_list_delete_link(stacking_list, it);
	if(raise) {
		for(it = g_list_last(stacking_list); it; it = next) {
			next = g_list_previous(it);
			if(WINDOW_IS_CLIENT(it->data)) {
				struct wm_client *ch = it->data;
				if(ch->layer == selected->layer && client_search_transient(selected, ch)) {
					if(client_is_direct_child(selected, ch)) {
						if(ch->modal)
							modals = g_list_prepend(modals, ch);
						else
							trans = g_list_prepend(trans, ch);
					} else if(client_helper(ch)) {
						if(selected->transient) {
							continue;
						}
						group_helpers = g_list_prepend(group_helpers, ch);
					} else {
						if(ch->modal)
							group_modals = g_list_prepend(group_modals, ch);
						else
							group_trans = g_list_prepend(group_trans, ch);
					}
					stacking_list = g_list_delete_link(stacking_list, it);
				}
			}
		}
	}
	wins = g_list_concat(modals, trans);
	wins = g_list_concat(wins, group_helpers);
	wins = g_list_append(wins, selected);
	if(selected->transient_for_group) {
		if(selected->modal) {
			wins = g_list_concat(wins, group_modals);
			group_modals = NULL;
		}
		wins = g_list_concat(wins, group_trans);
		group_trans = NULL;
	}
	last = NULL;
	for(it = g_list_last(stacking_list); it; it = g_list_previous(it)) {
		if(window_layer(it->data) < selected->layer) {
			last = it;
			continue;
		}
		if(!raise)
			break;
		if(window_layer(it->data) > selected->layer)
			break;
		last = it;
	}
	below = last;
	for(it = stacking_list; it; it = g_list_next(it)) {
		if(window_layer(it->data) > selected->layer)
			continue;
		if(window_layer(it->data) < selected->layer)
			break;
		if(WINDOW_IS_CLIENT(it->data)) {
			struct wm_client *c = it->data;
			if(c->group == selected->group)
				break;
		}
		if(it == below)
			break;
	}
	above = it ? g_list_previous(it) : g_list_last(stacking_list);
	if(below)
		it = g_list_previous(below);
	else
		it = g_list_last(stacking_list);
	for(; it != above; it = next) {
		next = g_list_previous(it);
		wins = g_list_prepend(wins, it->data);
		stacking_list = g_list_delete_link(stacking_list, it);
	}
	wins = g_list_concat(group_trans, wins);
	wins = g_list_concat(group_modals, wins);
	do_restack(wins, below);
	g_list_free(wins);
	if(!raise && selected->parents) {
		GSList *parents_copy, *sit;
		GSList *reorder = NULL;
		parents_copy = g_slist_copy(selected->parents);
		for(it = g_list_last(stacking_list); it && parents_copy; it = g_list_previous(it))
			if((sit = g_slist_find(parents_copy, it->data))) {
				reorder = g_slist_prepend(reorder, sit->data);
				parents_copy = g_slist_delete_link(parents_copy, sit);
			}
		g_assert(parents_copy == NULL);
		for(sit = reorder; sit; sit = g_slist_next(sit))
			restack_windows(sit->data, raise);
	}
}

void stacking_raise(struct wm_window *window)
{
	if(WINDOW_IS_CLIENT(window)) {
		struct wm_client *selected;
		selected = WINDOW_AS_CLIENT(window);
		restack_windows(selected, TRUE);
	} else {
		GList *wins;
		wins = g_list_append(NULL, window);
		stacking_list = g_list_remove(stacking_list, window);
		do_raise(wins);
		g_list_free(wins);
	} stacking_list_tail = g_list_last(stacking_list);
}

void stacking_lower(struct wm_window *window)
{
	if(WINDOW_IS_CLIENT(window)) {
		struct wm_client *selected;
		selected = WINDOW_AS_CLIENT(window);
		restack_windows(selected, FALSE);
	} else {
		GList *wins;
		wins = g_list_append(NULL, window);
		stacking_list = g_list_remove(stacking_list, window);
		do_lower(wins);
		g_list_free(wins);
	} stacking_list_tail = g_list_last(stacking_list);
}

void stacking_add(struct wm_window *win)
{
	g_assert(screen_support_win != None);
	if(WINDOW_IS_CLIENT(win))
		g_assert(WINDOW_AS_CLIENT(win)->managed);
	stacking_list = g_list_append(stacking_list, win);
	stacking_raise(win);
}

static GList *find_highest_relative(struct wm_client *client)
{
	GList *ret = NULL;
	if(client->parents) {
		GList *it;
		GSList *top;
		top = client_search_all_top_parents_layer(client);
		for(it = stacking_list; !ret && it; it = g_list_next(it)) {
			if(WINDOW_IS_CLIENT(it->data)) {
				struct wm_client *c = it->data;
				if(c->layer == client->layer && (c->desktop == client->desktop || c->desktop == DESKTOP_ALL
						|| client->desktop == DESKTOP_ALL)) {
					GSList *sit;
					for(sit = top; !ret && sit; sit = g_slist_next(sit)) {
						struct wm_client *topc = sit->data;
						if(topc == c || client_search_transient(topc, c))
							ret = it;
					}
				}
			}
		}
	}
	return ret;
}

void stacking_add_nonintrusive(struct wm_window *win)
{
	struct wm_client *client;
	GList *it_below = NULL;
	GList *it_above;
	GList *wins;
	if(!WINDOW_IS_CLIENT(win)) {
		stacking_add(win);
		return;
	}
	client = WINDOW_AS_CLIENT(win);
	g_assert(client->managed);
	it_below = find_highest_relative(client);
	if(!it_below) {
		if(focus_client && client != focus_client && focus_client->layer == client->layer) {
			it_below = g_list_find(stacking_list, focus_client);
			it_below = g_list_next(it_below);
		} else {
			it_below = stacking_list;
		}
	}
	for(; it_below; it_below = g_list_next(it_below)) {
		if(client->layer >= window_layer(it_below->data))
			break;
	}
	for(; it_below != stacking_list; it_below = it_above) {
		it_above = it_below ? g_list_previous(it_below) : g_list_last(stacking_list);
		if(client->layer <= window_layer(it_above->data))
			break;
	}
	wins = g_list_append(NULL, win);
	do_restack(wins, it_below);
	g_list_free(wins);
	stacking_list_tail = g_list_last(stacking_list);
}

static gboolean stacking_occluded(struct wm_client *client, struct wm_client *sibling)
{
	GList *it;
	gboolean occluded = FALSE;
	if(sibling && client->layer != sibling->layer)
		return occluded;
	for(it = g_list_previous(g_list_find(stacking_list, client)); it; it = g_list_previous(it))
		if(WINDOW_IS_CLIENT(it->data)) {
			struct wm_client *c = it->data;
			if((c->desktop == DESKTOP_ALL || client->desktop == DESKTOP_ALL || c->desktop == client->desktop)
				&& !client_search_transient(client, c)) {
				if(RECT_INTERSECTS_RECT(c->frame->area, client->frame->area)) {
					if(sibling != NULL) {
						if(c == sibling) {
							occluded = TRUE;
							break;
						}
					} else if(c->layer == client->layer) {
						occluded = TRUE;
						break;
					} else if(c->layer > client->layer)
						break;
				}
			}
		}
	return occluded;
}

static gboolean stacking_occludes(struct wm_client *client, struct wm_client *sibling)
{
	GList *it;
	gboolean occludes = FALSE;
	if(sibling && client->layer != sibling->layer)
		return occludes;
	for(it = g_list_next(g_list_find(stacking_list, client)); it; it = g_list_next(it))
		if(WINDOW_IS_CLIENT(it->data)) {
			struct wm_client *c = it->data;
			if((c->desktop == DESKTOP_ALL || client->desktop == DESKTOP_ALL || c->desktop == client->desktop)
				&& !client_search_transient(c, client)) {
				if(RECT_INTERSECTS_RECT(c->frame->area, client->frame->area)) {
					if(sibling != NULL) {
						if(c == sibling) {
							occludes = TRUE;
							break;
						}
					} else if(c->layer == client->layer) {
						occludes = TRUE;
						break;
					} else if(c->layer < client->layer)
						break;
				}
			}
		}
	return occludes;
}

gboolean stacking_restack_request(struct wm_client *client, struct wm_client *sibling, gint detail)
{
	gboolean ret = FALSE;
	if(sibling && (client->desktop != sibling->desktop && client->desktop != DESKTOP_ALL && sibling->desktop != DESKTOP_ALL)) {
		wm_debug("Setting restack sibling to NULL, they are not on the same desktop");
		sibling = NULL;
	}
	switch (detail) {
	case Below:
		wm_debug("Restack request Below for client %s sibling %s", client->title, sibling ? sibling->title : "(all)");
		stacking_lower(CLIENT_AS_WINDOW(client));
		ret = TRUE;
		break;
	case BottomIf:
		wm_debug("Restack request BottomIf for client %s sibling %s", client->title, sibling ? sibling->title : "(all)");
		if(stacking_occludes(client, sibling)) {
			stacking_lower(CLIENT_AS_WINDOW(client));
			ret = TRUE;
		}
		break;
	case Above:
		wm_debug("Restack request Above for client %s sibling %s", client->title, sibling ? sibling->title : "(all)");
		stacking_raise(CLIENT_AS_WINDOW(client));
		ret = TRUE;
		break;
	case TopIf:
		wm_debug("Restack request TopIf for client %s sibling %s", client->title, sibling ? sibling->title : "(all)");
		if(stacking_occluded(client, sibling)) {
			stacking_raise(CLIENT_AS_WINDOW(client));
			ret = TRUE;
		}
		break;
	case Opposite:
		wm_debug("Restack request Opposite for client %s sibling %s", client->title, sibling ? sibling->title : "(all)");
		if(stacking_occluded(client, sibling)) {
			stacking_raise(CLIENT_AS_WINDOW(client));
			ret = TRUE;
		} else if(stacking_occludes(client, sibling)) {
			stacking_lower(CLIENT_AS_WINDOW(client));
			ret = TRUE;
		}
		break;
	}
	return ret;
}
